package com.coldlz.mmp.app;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.coldlz.mmp.common.JacksonUtil;
import com.coldlz.mmp.pojo.HttpRecord;
import com.coldlz.mmp.pojo.TemplatePublisher;
import com.coldlz.mmp.proxy.HttpRecordRequest;
import com.coldlz.mmp.proxy.HttpRecordResponse;
import com.coldlz.mmp.proxy.ProxyInfo;
import com.coldlz.mmp.proxy.Recorder;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Service
public class HttpRecordService extends ServiceImpl<HttpRecordMapper, HttpRecord>
        implements Recorder, IService<HttpRecord>, ApplicationListener<ContextClosedEvent> {
    private final static Logger log = LoggerFactory.getLogger(HttpRecordService.class);
    private final JdbcTemplate jdbcTemplate;
    private static final String request_content_pathname = "data/request_content";
    private static final String response_content_pathname = "data/response_content";
    private static final File request_content_dir = new File(request_content_pathname);
    private static final File response_content_dir = new File(response_content_pathname);
    private final TemplatePublisher<HttpRecord> templatePublisher;
    public static final String tableName = "http_record";
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();
    public static final String create_table_sql = """
            CREATE TABLE "http_record" (
              "id" INTEGER NOT NULL,
              "remote_host" TEXT,
              "remote_port" integer,
              "status" TEXT comment 'padding  complete',
              "uri" TEXT,
              "uri_head" TEXT,
              "method" TEXT,
              "http_version" TEXT,
              "request_headers" TEXT,
              "request_content" TEXT,
              "request_content_type" TEXT,
              "request_content_len" integer,
              "response_headers" TEXT,
              "response_content" TEXT,
              "response_content_type" TEXT,
              "response_content_len" integer,
              "response_status" integer,
              "update_time" integer,
              "create_time" integer,
              "local_address" TEXT,
              "remote_address" TEXT,
              PRIMARY KEY ("id")
            )
            """;

    @PostConstruct
    public void init() {
        Consumer<File> createDir = file -> {
            if (file.exists()) {
                return;
            }
            boolean mkdirs1 = file.mkdirs();
            log.info("创建目录{} {}", file.getPath(), mkdirs1 ? "成功" : "失败");
        };
        Consumer<File> deleteSubFiles = file -> {
            if (file.isDirectory()) {
                File[] fs = file.listFiles();
                if (fs == null) {
                    return;
                }
                for (File f : fs) {
                    boolean delete = f.delete();
                }
            }
        };


        createDir.accept(response_content_dir);
        createDir.accept(request_content_dir);

        deleteSubFiles.accept(response_content_dir);
        deleteSubFiles.accept(request_content_dir);

        if (baseMapper.existsTable(tableName) == 0) {
            // 不存在，需要创建表
            jdbcTemplate.update(create_table_sql);
        } else {
            baseMapper.delete(new LambdaQueryWrapper<>());
        }
    }


    public HttpRecordService(JdbcTemplate jdbcTemplate
            , TemplatePublisher<HttpRecord> templatePublisher) {
        this.jdbcTemplate = jdbcTemplate;
        this.templatePublisher = templatePublisher;
    }

    @Override
    public void record(HttpRecordRequest request, ProxyInfo info) {
        HttpRecord httpRecord = new HttpRecord();
        String contentType;
        List<String> contentTypes = request.getHttpHeaders().getAll("Content-Type");
        if (contentTypes == null || contentTypes.isEmpty()) {
            contentType = "";
        } else {
            contentType = contentTypes.get(0);
        }
        httpRecord.setRequestContentType(contentType);
        String filePath = saveContent(request.getContent(), request_content_pathname, contentType);
        httpRecord.setRequestContentLen(request.getContent().stream().mapToLong(bytes -> bytes.length).sum());
        httpRecord.setUri(request.getUri());
        httpRecord.setUriHead(info.getProtocolName().name());
        httpRecord.setMethod(request.getMethod().name());
        httpRecord.setHttpVersion(request.getHttpVersion().text());
        httpRecord.setRequestHeaders(JacksonUtil.toJsonStr(request.getHeaders()));
        httpRecord.setRequestContent(filePath);
        httpRecord.setRequestContentType(contentType);
        httpRecord.setRemoteHost(info.getHost());
        httpRecord.setRemotePort(info.getPort());
        httpRecord.setLocalAddress(info.getLocalAddress());
        httpRecord.setRemoteAddress(info.getRemoteAddress());
        httpRecord.setCreateTime(System.currentTimeMillis());
        httpRecord.setStatus("padding");
        baseMapper.insert(httpRecord);

        request.setDbTableId(httpRecord.getId());
        templatePublisher.submit(httpRecord);
    }

    private static String saveContent(List<byte[]> contents, String pathname, String contentType) {
        String filename = System.currentTimeMillis() + "_" + IdUtil.fastSimpleUUID();
        String filePath = pathname + "/" + filename;
        File requestContentFile = new File(filePath);
        BufferedOutputStream bos = null;
        try {
            if (!requestContentFile.exists()) {
                boolean newFile = requestContentFile.createNewFile();
                if (!newFile) {
                    throw new IOException("创建文件失败");
                }
            }
            bos = new BufferedOutputStream(new FileOutputStream(requestContentFile));
            for (byte[] bytes : contents) {
                bos.write(bytes);
            }
        } catch (IOException e) {
            log.error("保存文件失败", e);
            filePath = "error";
        } finally {
            IoUtil.close(bos);
        }
        return filePath;
    }

    @Override
    public void record(HttpRecordResponse response) {

        Integer dbTableId = response.getDbTableId();
        List<byte[]> content = response.getContent();
        HttpHeaders httpHeaders = response.getHttpHeaders();
        Map<String, List<String>> headers = response.getHeaders();
        HttpResponseStatus status = response.getStatus();

        HttpRecord httpRecord = baseMapper.selectById(dbTableId);
        httpRecord.setId(dbTableId);


        List<String> contentTypes = httpHeaders.getAll("Content-Type");
        if (contentTypes == null || contentTypes.isEmpty()) {
            contentTypes = List.of("");
        }
        String contentType = contentTypes.get(0);
        httpRecord.setResponseContentType(contentType);
        String filePath = saveContent(content, response_content_pathname, contentType);
        httpRecord.setResponseContent(filePath);
        httpRecord.setUpdateTime(System.currentTimeMillis());
        httpRecord.setResponseHeaders(JacksonUtil.toJsonStr(headers));
        httpRecord.setResponseContentLen(content.stream().mapToLong(bytes -> bytes.length).sum());
        httpRecord.setStatus("complete");
        httpRecord.setResponseStatus(status.code());
        baseMapper.updateById(httpRecord);

        templatePublisher.submit(httpRecord);
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        close();
    }
    void close() {
        boolean terminated = executorService.isTerminated();
        if (!terminated) {
            executorService.shutdown();
            boolean interrupted = false;
            while (!terminated) {
                try {
                    terminated = executorService.awaitTermination(1L, TimeUnit.DAYS);
                } catch (InterruptedException e) {
                    if (!interrupted) {
                        executorService.shutdownNow();
                        interrupted = true;
                    }
                }
            }
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
